<?php

namespace Desktopd\MUA;


// SMTP client (MUA implementation)
class SMTPSubmission extends Submission {
    protected $submitter;
    protected $clientConfig; // SMTP client settings
    protected $tcpClient; // TCP client settings
    protected $tcpConnection; // TCP interface
    protected $connection; // SMTP Client Connection
    
    
    public function __construct (Submitter $submitter, SMTPClient $clientConfig) {
        $this->submitter = $submitter;
        $this->clientConfig = $clientConfig;
        
        $tcpClient = new TCPClient();
        if ($clientConfig->isPFSRequired()) {
            // PFS required
            $tcpClient->setCipherList(TCPClient::TLS_CIPHERS_SECURE);
        } elseif ($clientConfig->getCiphers()) {
            $tcpClient->setCipherList($clientConfig->getCiphers());
        }
        
        $this->tcpClient = $tcpClient;
        
    }
    
    protected function makeConnection () {
        if ($this->connection instanceof SMTPClientConnection) {
            return $this->keepAlive();
        }
        
        $clientConfig = $this->clientConfig;
        $tcpClient = $this->tcpClient;
        
        $flags = 0;
        if ($clientConfig->isInsecure()) $flags |= TCPClient::FLAG_TLS_INSECURE;
        if ($clientConfig->isSMTPS()) $flags |= TCPClient::FLAG_TLS_FULL;
        
        $host = $clientConfig->getHost();
        $port = $clientConfig->getPort();
        
        $tcpConnection = $tcpClient->createConnection($host, $port, $flags);
        $this->tcpConnection = $tcpConnection;
        
        $connection = new SMTPClientConnection($tcpConnection);
        $this->connection = $connection;
        $connection->addDebugListener(function ($data) {
            $this->submitter->callDebugger($data);
        });
        
        if ($connection->isTLS()) {
            if (!$this->checkTLS()) {
                throw new \RuntimeException('Failed: TLS check');
            }
        }
        
        if (!$connection->readGreetings()) {
            throw new \RuntimeException('readGreetings(): failed');
        }
        
        if (!$connection->sayHello()) {
            throw new \RuntimeException('sayHello(): failed');
        }
        
        if ($clientConfig->isSTARTTLS() && (!$connection->isTLS())) {
            if (!$connection->startTLS()) {
                throw new \RuntimeException('STARTTLS: failed');
            }
            
            if (!$this->checkTLS()) {
                throw new \RuntimeException('Failed: TLS check');
            }
        }
        
        if ($clientConfig->isAuthenticationEnabled()) {
            $user = $clientConfig->getUser();
            $password = $clientConfig->getPassword();
            
            if (!$connection->authenticate($user, $password)) {
                throw new \RuntimeException('Failed to authenticate');
            }
        }
        
        return $this->connection;
    }
    
    protected function checkTLS () {
        $tcpConnection = $this->tcpConnection;
        $clientConfig = $this->clientConfig;
        
        $pinnedCert = $clientConfig->getPinnedCertificate();
        if ($pinnedCert) {
            if (!$tcpConnection->requireCertificate($pinnedCert)) {
                throw new \RuntimeException('The server certificate does not match the pinned one');
                //return false;
            }
        }
        
        return true;
    }
    
    public function keepAlive () {
        if (!$this->connection->isConnected()) {
            throw new \RuntimeException('Not connected');
        }
        
        return $this->connection->noop();
    }
    
    public function sendMessage (NewMessage $message) {
        if (1 > count($message)) {
            throw new \RuntimeException('No recipients specified');
        }
        
        $message->composeAll();
        
        if (!$this->makeConnection()) {
            throw new \RuntimeException('Could not connect to the SMTP server');
        }
        
        $sender = $this->submitter->getSenderAddress();
        if (!$this->connection->mailFrom($sender)) {
            throw new \RuntimeException('mailFrom(): failed');
        }
        
        $recipients = $message->getRecipients();
        foreach ($recipients as $recipient) {
            if (!$this->connection->addRecipient($recipient)) {
                throw new \RuntimeException('addRecipient(): failed');
            }
        }
        
        if (!$this->connection->sendData($message)) {
            throw new \RuntimeException('sendData(): failed');
        }
        
        return true;
    }
    
    public function __destruct () {
        if ($this->connection instanceof SMTPClientConnection) {
            if ($this->connection->isConnected()) {
                $this->connection->quit();
            }
        }
    }
}


// vim: ts=4 et ai

